package com.iwdnb.gkgz.common.quota;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.alibaba.fastjson.annotation.JSONField;

import com.google.common.collect.Lists;
import com.iwdnb.bmnf.common.utils.BeanConvertUtils;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import static com.iwdnb.gkgz.common.utils.BigDecimalUtils.ten;

public class KdjIndicator {

    private static final BigDecimal HIGH_VALUE = BigDecimalUtils.eightyFive;
    private static final BigDecimal LOW_VALUE = BigDecimalUtils.twentyFive;

    /**
     * 计算Kdj指标
     *
     * @param list
     * @return
     */
    public static List<Kdj> calculateKdj(List<StockDayData> list) {
        return calculateKdj(list, 9, 3, 3);
    }

    /**
     * 计算Kdj指标
     *
     * @param list
     * @param n
     * @param m1
     * @param m2
     * @return
     */
    public static List<Kdj> calculateKdj(List<StockDayData> list, int n, int m1, int m2) {
        List<Double> closingPrices = new ArrayList<>();
        List<Double> lowPrices = new ArrayList<>();
        List<Double> highPrices = new ArrayList<>();

        for (StockDayData data : list) {
            closingPrices.add(data.getClosePrice().doubleValue());
        }
        return calculateKdj(list, closingPrices, n, m1, m2);

    }

    /**
     * 计算Kdj指标
     *
     * @param list
     * @param closePrices
     * @param lowPrices
     * @param highPrices
     * @param n
     * @param m1
     * @param m2
     * @return
     */
    public static List<Kdj> calculateKdj(List<StockDayData> list, List<Double> closePrices, int n, int m1, int m2) {
        List<Kdj> kdjList = new ArrayList<>();
        double rsv = 0;
        double k = 50;
        double d = 50;
        double j = 0;
        for (int i = 0; i < closePrices.size(); i++) {
            StockDayData stockDayData = list.get(i);
            if (i >= n - 1) {
                double c = closePrices.get(i);
                double l = getLowest(closePrices, i, n);
                double h = getHighest(closePrices, i, n);
                rsv = (c - l) / (h - l) * 100;
                k = ((m1 - 1) * k + rsv) / (m1);
                d = ((m2 - 1) * d + k) / (m2);
                j = 3 * k - 2 * d;

            }
            Kdj kdj = new Kdj(stockDayData.getDatetime(), BigDecimalUtils.of(k), BigDecimalUtils.of(d),
                BigDecimalUtils.of(j), stockDayData.getClosePrice());
            kdjList.add(kdj);
            stockDayData.setKdj(kdj);
        }

        return kdjList;
    }

    // 获取n周期内的最低价
    private static double getLowest(List<Double> prices, int end, int n) {
        double lowest = Double.MAX_VALUE;
        for (int i = end; i > end - n; i--) {
            if (prices.get(i) < lowest) {
                lowest = prices.get(i);
            }
        }
        return lowest;
    }

    // 获取n周期内的最高价
    private static double getHighest(List<Double> prices, int end, int n) {
        double highest = Double.MIN_VALUE;
        for (int i = end; i > end - n; i--) {
            if (prices.get(i) > highest) {
                highest = prices.get(i);
            }
        }
        return highest;
    }

    /**
     * 获取kdj波段区域列表
     *
     * @param dataList
     * @return
     */
    public static List<KdjRegion> getKdjRegionList(List<StockDayData> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            return null;
        }
        int index = 0;
        int maxIndex = 0;
        int minIndex = 0;
        int beginIndex = 0;
        StockDayData beginData = dataList.get(0);
        Kdj begin = beginData.getKdj();
        Kdj max = beginData.getKdj();
        Kdj min = beginData.getKdj();
        boolean upFlag = false;
        boolean downFlag = false;

        List<KdjRegion> kdjRegionList = new ArrayList<>();
        for (int i = 0; i < dataList.size(); i++) {
            StockDayData data = dataList.get(i);
            Kdj kdj = data.getKdj();
            //j值下穿20
            if (downFlag) {
                //下一个J值上穿20，标志为区间结束
                if (i + 1 < dataList.size() - 1) {
                    StockDayData nextData = dataList.get(i + 1);
                    //计算下跌区间
                    if (BigDecimalUtils.isGt(nextData.getKdj().getK(), LOW_VALUE)) {
                        KdjRegion kdjRegion = new KdjRegion();
                        kdjRegion.setUpFlag(BooleanUtils.FALSE);
                        kdjRegion.setBegin(BeanConvertUtils.convert(begin, Kdj.class));
                        kdjRegion.setEnd(BeanConvertUtils.convert(data.getKdj(), Kdj.class));
                        kdjRegion.setLimit(BeanConvertUtils.convert(min, Kdj.class));
                        kdjRegion.setDataList(dataList.subList(beginIndex, i).stream().map(StockDayData::getKdj)
                            .collect(Collectors.toList()));
                        kdjRegion.setLength(dataList.size());
                        kdjRegion.setLimitIndex(minIndex - beginIndex);
                        kdjRegion.setRegionValue(getRegionValue(kdjRegion));
                        if (kdjRegion.getLength() > 5) {
                            kdjRegionList.add(kdjRegion);
                        }
                        begin = kdj;
                        beginIndex = 0;
                        max = begin;
                        min = begin;
                        maxIndex = 0;
                        minIndex = 0;
                        upFlag = false;
                        downFlag = false;
                    }
                }
            } else if (upFlag) {
                //j值上穿80
                //下一个J值下穿80，标志为区间结束
                if (i + 1 < dataList.size() - 1) {
                    StockDayData nextData = dataList.get(i + 1);
                    if (BigDecimalUtils.isLt(nextData.getKdj().getK(), HIGH_VALUE)) {
                        KdjRegion kdjRegion = new KdjRegion();
                        kdjRegion.setUpFlag(BooleanUtils.TRUE);
                        kdjRegion.setBegin(BeanConvertUtils.convert(begin, Kdj.class));
                        kdjRegion.setEnd(BeanConvertUtils.convert(data.getKdj(), Kdj.class));
                        kdjRegion.setLimit(BeanConvertUtils.convert(max, Kdj.class));
                        kdjRegion.setDataList(dataList.subList(beginIndex, i).stream().map(StockDayData::getKdj)
                            .collect(Collectors.toList()));
                        kdjRegion.setLength(dataList.size());
                        kdjRegion.setLimitIndex(maxIndex - beginIndex);
                        kdjRegion.setRegionValue(getRegionValue(kdjRegion));
                        if (kdjRegion.getLength() > 5) {
                            kdjRegionList.add(kdjRegion);
                        }
                        begin = kdj;
                        beginIndex = 0;
                        max = begin;
                        min = begin;
                        minIndex = 0;
                        maxIndex = 0;
                        upFlag = false;
                        downFlag = false;
                    }
                }

            }
            //循环处理数据
            //计算最大值和最小值
            if (BigDecimalUtils.isGt(kdj.getK(), max.getK())) {
                max = kdj;
                maxIndex = index;
                if (!upFlag && isHigh(kdj)) {
                    upFlag = true;
                    begin = kdj;
                    beginIndex = index;
                }
            }
            if (BigDecimalUtils.isLt(kdj.getK(), min.getK())) {
                min = kdj;
                minIndex = index;
                if (!downFlag && isLow(kdj)) {
                    downFlag = true;
                    begin = kdj;
                    beginIndex = index;
                }
            }
            index++;

        }
        //补上最后一段kdj区间
        if (beginIndex > 0) {
            KdjRegion kdjRegion = new KdjRegion();
            if (isHigh(begin)) {
                kdjRegion.setUpFlag(BooleanUtils.TRUE);
                kdjRegion.setLimit(BeanConvertUtils.convert(max, Kdj.class));
                kdjRegion.setLimitIndex(maxIndex - beginIndex);

            } else if (isLow(begin)) {
                kdjRegion.setUpFlag(BooleanUtils.FALSE);
                kdjRegion.setLimit(BeanConvertUtils.convert(min, Kdj.class));
                kdjRegion.setLimitIndex(minIndex - beginIndex);
            }
            Kdj limit = kdjRegion.getLimit();
            kdjRegion.setBegin(begin);
            kdjRegion.setEnd(dataList.get(dataList.size() - 1).getKdj());
            kdjRegion.setDataList(dataList.subList(beginIndex, dataList.size()).stream().map(StockDayData::getKdj)
                .collect(Collectors.toList()));
            kdjRegion.setLength(dataList.size());
            kdjRegion.setRegionValue(getRegionValue(kdjRegion));
            if (kdjRegion.getLength() > 5) {
                //如果最高(低)点时间不是最后一个,或者进入超买超卖，加入到kdj区域中
                if (!StringUtils.equals(limit.getDatetime(), kdjRegion.getEnd().getDatetime())
                    || isActive(limit) || isNegative(limit)) {
                    kdjRegionList.add(kdjRegion);
                }
            }

        }
        setOperateInLimit(kdjRegionList, 1);
        return kdjRegionList;
    }

    /**
     * 获取底背离区间
     *
     * @param dataList
     * @return
     */
    public static List<KdjRegion> caculateBottomDivergence(List<StockDayData> dataList, BigDecimal limitValue) {
        dataList = IndicatorUtils.fixDatetime(dataList);
        List<KdjRegion> regionList = getKdjRegionList(dataList);
        if (CollectionUtils.isEmpty(regionList)) {
            return null;
        }
        List<KdjRegion> lowKdjRegionList = regionList.stream().filter(t -> BooleanUtils.isFalse(t.getUpFlag())).collect(
            Collectors.toList());

        KdjRegion beforeRegion = lowKdjRegionList.get(0);
        Kdj beforeLimit = beforeRegion.getLimit();
        List<KdjRegion> divergenceList = new ArrayList<>();
        for (int i = 1; i < lowKdjRegionList.size(); i++) {
            KdjRegion kdjRegion = lowKdjRegionList.get(i);
            Kdj limit = kdjRegion.getLimit();

            Kdj kdj = getKdjAfterMin(kdjRegion, 0);
            //未找到反弹的kdj柱，跳过该区间
            if (Objects.isNull(kdj)) {
                beforeLimit = limit;
                continue;
            }
            //kdj区间值变小，但是股价继续下跌，则存在底背离
            if (BigDecimalUtils.isLt(kdjRegion.getRegionValue().abs(), beforeRegion.getRegionValue().abs())
                && BigDecimalUtils.isLt(limit.getPrice(), beforeLimit.getPrice())) {
                divergenceList.add(kdjRegion);
            }
            //kdj柱大于-20，不加入底背离对比
            if (BigDecimalUtils.isGt(limit.getK(), limitValue)) {
                continue;
            }
            beforeRegion = kdjRegion;
            beforeLimit = limit;
        }
        setRemark(divergenceList, "kdjBottomDivergence");
        return divergenceList;
    }

    /**
     * 获取J值负值区间
     *
     * @param dataList
     * @return
     */
    public static List<KdjRegion> caculateJValueNegativeRegion(List<StockDayData> dataList) {
        dataList = IndicatorUtils.fixDatetime(dataList);
        List<KdjRegion> regionList = getKdjRegionList(dataList);
        if (CollectionUtils.isEmpty(regionList)) {
            return null;
        }
        List<KdjRegion> lowKdjRegionList = regionList.stream().filter(t -> BooleanUtils.isFalse(t.getUpFlag())).collect(
            Collectors.toList());
        List<KdjRegion> negativeRegionList = new ArrayList<>();
        for (int i = 0; i < lowKdjRegionList.size(); i++) {
            KdjRegion kdjRegion = lowKdjRegionList.get(i);
            int negativeCount = 0;
            for (Kdj kdj : kdjRegion.getDataList()) {
                if (isNegative(kdj)) {
                    negativeCount++;
                }
            }
            if (negativeCount >= 1) {
                negativeRegionList.add(kdjRegion);
            }
        }
        setRemark(negativeRegionList, "kdjNegative");
        setOperateInLimit(negativeRegionList, 0);
        return negativeRegionList;
    }

    /**
     * 获取J值正值区间
     *
     * @param dataList
     * @return
     */
    public static List<KdjRegion> caculateJValueActiveRegion(List<StockDayData> dataList) {
        dataList = IndicatorUtils.fixDatetime(dataList);
        List<KdjRegion> regionList = getKdjRegionList(dataList);
        if (CollectionUtils.isEmpty(regionList)) {
            return null;
        }
        List<KdjRegion> kdjRegionList = regionList.stream().filter(t -> BooleanUtils.isTrue(t.getUpFlag())).collect(
            Collectors.toList());
        List<KdjRegion> activeRegionList = new ArrayList<>();
        for (int i = 0; i < kdjRegionList.size(); i++) {
            KdjRegion kdjRegion = kdjRegionList.get(i);
            int activeCount = 0;
            for (Kdj kdj : kdjRegion.getDataList()) {
                if (isActive(kdj)) {
                    activeCount++;
                }
            }
            if (activeCount > 0) {
                activeRegionList.add(kdjRegion);
            }
        }
        setRemark(activeRegionList, "kdjActive");
        setOperateInLimit(activeRegionList, 0);
        return activeRegionList;
    }

    private static boolean isHigh(Kdj kdj) {
        return BigDecimalUtils.isGt(kdj.getK(), HIGH_VALUE);
    }

    private static boolean isLow(Kdj kdj) {
        return BigDecimalUtils.isLt(kdj.getK(), LOW_VALUE);
    }

    private static boolean isActive(Kdj kdj) {
        return BigDecimalUtils.isGt(kdj.getJ(), BigDecimalUtils.oneHundredTen)
            && BigDecimalUtils.isGt(kdj.getK(), BigDecimalUtils.ninety);
    }

    private static boolean isNegative(Kdj kdj) {
        return BigDecimalUtils.isLt(kdj.getJ(), BigDecimalUtils.fuTwenty)
            && BigDecimalUtils.isLt(kdj.getK(), ten);
    }

    /**
     * 获取kdj区间块最低值反弹的第一个值
     *
     * @param kdjRegion
     * @param beforeClosePrice
     * @return
     */
    public static Kdj getKdjAfterMin(KdjRegion region, int index) {
        List<Kdj> dataList = region.getDataList();
        if (CollectionUtils.isEmpty(dataList)) {
            return null;
        }
        //获取kdj区间最小值
        Kdj minData = dataList.get(0);
        int upCount = 0;
        for (int i = index; i < dataList.size(); i++) {
            Kdj kdj = dataList.get(i);
            if (BigDecimalUtils.isLt(kdj.getJ(), minData.getJ())) {
                minData = kdj;
            } else {
                upCount++;
            }
            //反弹2日,即设为最小值
            if (upCount >= 2) {
                break;
            }
        }

        int afterCount = 0;
        int lowCount = 0;
        BigDecimal minBar = minData.getJ();
        for (int j = index; j < dataList.size(); j++) {
            Kdj kdj = dataList.get(j);
            //在最小值之前忽略
            if (DateUtils.beforeOrEquals(kdj.getDatetime(), minData.getDatetime())) {
                continue;
            }
            afterCount++;
            BigDecimal kdjBar = kdj.getJ();
            BigDecimal kdjRate = BigDecimalUtils.subtract(kdjBar, minBar);
            if (BigDecimalUtils.isGe(kdjRate, ten)) {
                //kdjJ值回升，且价格持续回升
                if (BigDecimalUtils.isGt(kdj.getPrice(), minData.getPrice())) {
                    lowCount++;
                } else {
                    lowCount = 0;
                }
                if (lowCount >= 2) {
                    return kdj;
                }
            }
            //最小值2日后还未获取反转kdj，说明还在下跌
            if (afterCount >= 2) {
                return getKdjAfterMin(region, j);
            }
        }
        //最小值是最后一条数据，说明波段反转迅速或者处于进行中，跳过
        if (BigDecimalUtils.eq(minBar, region.getEnd().getJ())) {
            return null;
        }
        return region.getEnd();
    }

    private static BigDecimal getRegionValue(KdjRegion region) {
        List<Kdj> kdjList = region.getDataList().subList(0, region.getLimitIndex() + 1);
        BigDecimal value = BigDecimal.ZERO;
        for (Kdj kdj : kdjList) {
            if (BooleanUtils.isTrue(region.getUpFlag()) && BigDecimalUtils.isGe(kdj.getJ(), kdj.getK())) {
                value = value.add(kdj.getJ()).subtract(kdj.getK());
            } else if (BooleanUtils.isFalse(region.getUpFlag()) && BigDecimalUtils.isLe(kdj.getJ(), kdj.getK())) {
                value = value.add(kdj.getK()).subtract(kdj.getJ());
            }
        }
        return value;
    }

    /**
     * Kdj指标类
     */
    @Data
    public static class Kdj {
        private String datetime;
        private BigDecimal k;
        private BigDecimal d;
        private BigDecimal j;
        private BigDecimal price;

        public Kdj() {
        }

        public Kdj(String datetime, BigDecimal k, BigDecimal d, BigDecimal j, BigDecimal price) {
            this.datetime = datetime;
            this.k = k;
            this.d = d;
            this.j = j;
            this.price = price;
        }
    }

    public static void setRemark(List<KdjRegion> regionList, String remark) {
        for (KdjRegion region : regionList) {
            region.setRemark(StringUtils.isBlank(region.getRemark()) ? remark : region.getRemark() + "," + remark);
        }
    }

    private static void setOperateInLimit(List<KdjRegion> regionList, int operateInLimitIndex) {
        for (KdjRegion region : regionList) {
            region.setOperateInLimitIndex(operateInLimitIndex);
        }
    }

    /**
     * kdj区间
     */
    @Data
    public static class KdjRegion {
        private Kdj begin;

        private Kdj end;

        /**
         * 最高或最低值
         */
        private Kdj limit;

        @JSONField(serialize = false)
        private List<Kdj> dataList;

        private int length;
        /**
         * 最高或者最低的索引位置
         */
        private int limitIndex;
        /**
         * 波段值,用长度*最高柱表示
         */
        private BigDecimal regionValue;

        private String upFlag;

        private String remark;
        /**
         * 极限值日期索引后几天操作,0为当天操作
         */
        private int operateInLimitIndex;
    }

    public static void main(String[] args) {
        List<Double> closePrices = Lists.newArrayList(100.0, 102.0, 101.0, 105.0, 107.0, 106.0, 108.0, 110.0, 109.0,
            112.0);
        List<Double> lowPrices = Lists.newArrayList(98.0, 100.0, 99.0, 103.0, 105.0, 104.0, 106.0, 108.0, 107.0, 110.0);
        List<Double> highPrices = Lists.newArrayList(102.0, 104.0, 103.0, 107.0, 109.0, 108.0, 110.0, 112.0, 111.0,
            113.0);

        int n = 9; // 计算周期
        int m1 = 3; // K值平滑周期
        int m2 = 3; // D值平滑周期

        //List<Kdj> kdjList = calculateKdj(closePrices, lowPrices, highPrices, n, m1, m2);
        //
        //for (Kdj kdj : kdjList) {
        //    System.out.println(kdj);
        //}
    }
}

